package com.gxy.learn.pay.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.gxy.learn.cu.utils.date.DateUtils;
import com.gxy.learn.cu.utils.random.RandomCodeUtils;
import com.gxy.learn.pay.common.Result;
import com.gxy.learn.pay.config.AliPayConfig;
import com.gxy.learn.pay.controller.param.AliPayOrderReqParam;
import com.gxy.learn.pay.entity.PayOrder;
import com.gxy.learn.pay.enums.AliPayStatusTypeEnum;
import com.gxy.learn.pay.enums.AliPayTradeStatusEnum;
import com.gxy.learn.pay.enums.PayOrderTypeEnum;
import com.gxy.learn.pay.repository.PayOrderRepository;
import com.gxy.learn.pay.service.PayService;
import com.gxy.learn.pay.vo.AlipayOrderResVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 支付实现
 */
@Slf4j
@Service
public class PayServiceImpl implements PayService {
    @Autowired
    private AliPayConfig aliPayConfig;
    @Autowired
    private PayOrderRepository payOrderRepository;

    @Override
    public Result aliPayCreateOrder(String commodityIds) {
        log.info("commodityIds = {}", commodityIds);
        String errMsg;
        if (StringUtils.isBlank(commodityIds)) {
            errMsg = "商户订单号为空";
            log.error(errMsg);
            return Result.error(errMsg);
        }
        String appId = aliPayConfig.getAppid();
        String appPrivateKey = aliPayConfig.getPrivateKey();
        String alipayPublicKey = aliPayConfig.getPublicKey();
        String signType = aliPayConfig.getSingType();
        String serverUrl = aliPayConfig.getUrl();
        String notifyUrl = aliPayConfig.getNotifyUrl();
        String charset = aliPayConfig.getCharset();
        if (StringUtils.isAnyBlank(appId, appPrivateKey, alipayPublicKey, signType, serverUrl, notifyUrl, charset)) {
            log.error("aliPayConfig = {}", JSONObject.toJSON(aliPayConfig));
            return Result.error("支付宝创建订单失败！原因：支付宝配置信息错误,请检查！");
        }
        //商户订单号。 由商家自定义，64个字符以内，仅支持字母、数字、下划线且需保证在商户端不重复。
        String outTradeNo = RandomCodeUtils.random32NumStr();
        PayOrder byOutTradeNo = payOrderRepository.findOneByOutTradeNo(outTradeNo);
        if (null != byOutTradeNo) {
            while (true) {
                outTradeNo = RandomCodeUtils.random32NumStr();
                byOutTradeNo = payOrderRepository.findOneByOutTradeNo(outTradeNo);
//                直到生成唯一随机码为止！
                if (null == byOutTradeNo) {
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    log.error("", e);
                }
            }
        }
        //=============================================根据商品id集合查询商品组装订单新==============================================
//        目前缺失订单表，暂时用以下数据测试
        PayOrder order = new PayOrder(appId, outTradeNo, "0.01", "商品测试", null, PayOrderTypeEnum.ALIPAY.getCode());
        //=============================================根据商品id集合查询商品组装订单新==============================================
        order.setOrderStatus(AliPayStatusTypeEnum.ORDER_SUCCESS.getVal());
        /**
         * 根据商品id 查询商品信息进行下单
         *
         * 这里暂时写死 测试
         */
        try {
            //实例化客户端
            AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, appPrivateKey, "json", charset, alipayPublicKey, signType);
            //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称：alipay.trade.app.pay
            AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
            //SDK已经封装掉了公共参数，这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
            AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
            model.setBody(order.getBizContent());
            model.setSubject(order.getSubject());
            model.setOutTradeNo(order.getOutTradeNo());
            model.setTimeoutExpress("30m");
            model.setTotalAmount(order.getTotalAmount());
            model.setProductCode("QUICK_MSECURITY_PAY");
            request.setBizModel(model);
            request.setNotifyUrl(notifyUrl);
            order.setRequestInfo(JSONObject.toJSONString(request));
            //这里和普通的接口调用不同，使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            order.setResponseInfo(JSONObject.toJSONString(response.getBody()));
            if (response.isSuccess()) {
                payOrderRepository.save(order);
                AlipayOrderResVo res = new AlipayOrderResVo();
                res.setResponseInfo(response.getBody());
                res.setOutTradeNo(outTradeNo);
                return Result.success("支付宝订单创建成功", res);
            } else {
                errMsg = String.format("支付宝订单创建失败！原因：%s", response.getSubMsg());
                log.error(errMsg);
                return Result.error(errMsg);
            }
        } catch (AlipayApiException e) {
            order.setOrderStatus(AliPayStatusTypeEnum.ORDER_FAILURE.getVal());
            log.error("支付宝创建订单失败！", e);
            payOrderRepository.save(order);
            return Result.error("支付宝订单创建失败,请稍后重试！");
        }
    }

    @Override
    public Result aliPayH5CreateOrder(AliPayOrderReqParam reqParam) {
        log.info("reqParam = {}", reqParam);
        String errMsg;
        if (StringUtils.isBlank(reqParam.getCommodityIds())) {
            errMsg = "商户订单号为空";
            log.error(errMsg);
            return Result.error(errMsg);
        }
        String appId = aliPayConfig.getAppid();
        String appPrivateKey = aliPayConfig.getPrivateKey();
        String alipayPublicKey = aliPayConfig.getPublicKey();
        String signType = aliPayConfig.getSingType();
        String serverUrl = aliPayConfig.getUrl();
        String notifyUrl = aliPayConfig.getNotifyUrl();
        String returnUrl = aliPayConfig.getReturnUrl();
        String charset = aliPayConfig.getCharset();

        if (StringUtils.isAnyBlank(appId, appPrivateKey, alipayPublicKey, signType, serverUrl, notifyUrl, charset)) {
            log.error("aliPayConfig = {}", JSONObject.toJSON(aliPayConfig));
            return Result.error("支付宝创建订单失败！原因：支付宝配置信息错误,请检查！");
        }
        //商户订单号。 由商家自定义，64个字符以内，仅支持字母、数字、下划线且需保证在商户端不重复。
        String outTradeNo = RandomCodeUtils.random32NumStr();
        PayOrder byOutTradeNo = payOrderRepository.findOneByOutTradeNo(outTradeNo);
        if (null != byOutTradeNo) {
            while (true) {
                outTradeNo = RandomCodeUtils.random32NumStr();
                byOutTradeNo = payOrderRepository.findOneByOutTradeNo(outTradeNo);
//                直到生成唯一随机码为止！
                if (null == byOutTradeNo) {
                    break;
                }
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    log.error("", e);
                }
            }
        }
        //=============================================根据商品id集合查询商品组装订单新==============================================
//        目前缺失订单表，暂时用以下数据测试
        PayOrder order = new PayOrder(appId, outTradeNo, "0.01", "商品测试", null, PayOrderTypeEnum.ALIPAY.getCode());
        //=============================================根据商品id集合查询商品组装订单新==============================================
        order.setOrderStatus(AliPayStatusTypeEnum.ORDER_SUCCESS.getVal());
        AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, appPrivateKey, "json", charset, alipayPublicKey, signType);
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        request.setReturnUrl(returnUrl);
        request.setNotifyUrl(notifyUrl);
        JSONObject bizContent = new JSONObject();
        bizContent.put("out_trade_no", order.getOutTradeNo());
        bizContent.put("total_amount", order.getTotalAmount());
        bizContent.put("subject", order.getSubject());
        bizContent.put("product_code", "QUICK_WAP_WAY");
        //订单绝对超时时间 30分钟超时
        /**
         * 订单绝对超时时间。
         * 格式为yyyy-MM-dd HH:mm:ss。
         * 注：time_express和timeout_express两者只需传入一个或者都不传，如果两者都传，优先使用time_expire。
         */
        //bizContent.put("time_expire", "2022-08-01 22:00:00");
        /**
         * 订单相对超时时间。
         * 该笔订单允许的最晚付款时间，逾期将关闭交易。取值范围：5m～15d。m-分钟，h-小时，d-天，1c-当天（1c-当天的情况下，无论交易何时创建，都在0点关闭）。 该参数数值不接受小数点， 如 1.5h，可转换为 90m。
         * 注：无线支付场景最小值为5m，低于5m支付超时时间按5m计算。
         * 注：time_express和timeout_express两者只需传入一个或者都不传，如果两者都传，优先使用time_expire。
         */
        bizContent.put("timeout_express", "30m");

        //// 商品明细信息，按需传入
        //JSONArray goodsDetail = new JSONArray();
        //JSONObject goods1 = new JSONObject();
        //goods1.put("goods_id", "goodsNo1");
        //goods1.put("goods_name", "子商品1");
        //goods1.put("quantity", 1);
        //goods1.put("price", 0.01);
        //goodsDetail.add(goods1);
        //bizContent.put("goods_detail", goodsDetail);

        //// 扩展信息，按需传入
        //JSONObject extendParams = new JSONObject();
        //extendParams.put("sys_service_provider_id", "2088511833207846");
        //bizContent.put("extend_params", extendParams);

        order.setRequestInfo(JSONObject.toJSONString(request));
        request.setBizContent(bizContent.toString());
        try {
            AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
            //这里和普通的接口调用不同，使用的是sdkExecute
            order.setResponseInfo(JSONObject.toJSONString(response.getBody()));
            if (response.isSuccess()) {
                log.info("调用成功");
                payOrderRepository.save(order);
                AlipayOrderResVo res = new AlipayOrderResVo();
                res.setResponseInfo(response.getBody());
                res.setOutTradeNo(outTradeNo);
                return Result.success("支付宝订单创建成功", res);
            } else {
                log.info("调用失败");
                errMsg = String.format("支付宝订单创建失败！原因：%s", response.getSubMsg());
                log.error(errMsg);
                return Result.error(errMsg);
            }
        } catch (AlipayApiException e) {
            order.setOrderStatus(AliPayStatusTypeEnum.ORDER_FAILURE.getVal());
            log.error("支付宝创建订单失败！", e);
            payOrderRepository.save(order);
            return Result.error("支付宝订单创建失败,请稍后重试！");
        }
    }

    @Override
    public void aliPayNotify(HttpServletRequest request) {
        log.info("支付宝回调开始！");
        //https://opendocs.alipay.com/open/204/105301
//        获取支付宝POST过来反馈信息
        Map<String, String> params = new HashMap<String, String>(16);
        Map requestParams = request.getParameterMap();
        if (MapUtils.isEmpty(requestParams)) {
            log.error("未获取到回调参数，回调结束！");
            return;
        }
        /**
         * {"gmt_create":["2021-09-14 09:16:40"],"charset":["UTF-8"],"seller_email":["jszx@henansoft.com.cn"],"subject":["123"],"sign":["nnnZIdW4GwI5kfS55JvlC0oR+CZrj18C1mfxWFUDIhqj0vfXIqbmoG4RdKRbUV5pQVwkP+6xPdNtPKAfooLWDvkxQmZANn0uVNlzdlo9o2Vt8Zdc4aOcMbh744eGyHMZv6U5GH5i6yMqLePTUpELQsXfYvOIx/tWtnuQx2638y/FXPPMZuCjEMOY7CdFz1PO+YZfs4UMwMhrTMN6kwu9FI3KIGGW+87HbWq9yxartgurvkRBvuXWxIX5byem0GK2y/4ecWv4ObQUcJicvWDuEiN9PMuvouGC8Y6rgVMZnW6zga/ufBmA5BeV01Ivbh65Mw2AcsnvHY9zCJXHr1kpaw=="],"body":["123"],"buyer_id":["2088502870166685"],"invoice_amount":["0.01"],"notify_id":["2021091400222091641066681438260544"],"fund_bill_list":["[{\"amount\":\"0.01\",\"fundChannel\":\"PCREDIT\"}]"],"notify_type":["trade_status_sync"],"trade_status":["TRADE_SUCCESS"],"receipt_amount":["0.01"],"app_id":["2021002177603904"],"buyer_pay_amount":["0.01"],"sign_type":["RSA2"],"seller_id":["2088331233875946"],"gmt_payment":["2021-09-14 09:16:40"],"notify_time":["2021-09-14 09:16:41"],"version":["1.0"],"out_trade_no":["595IKF7GU43LUEOTDHV4OOFFMRG5J1BK"],"total_amount":["0.01"],"trade_no":["2021091422001466681435654078"],"auth_app_id":["2021002177603904"],"buyer_logon_id":["ywa***@sohu.com"],"point_amount":["0.00"]}
         */
        log.info("回调参数params = {}", JSONObject.toJSONString(requestParams));
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
            }
            //乱码解决，这段代码在出现乱码时使用。
            params.put(name, valueStr);
        }
        //商户订单号
        String outTradeNo = params.get("out_trade_no");
        PayOrder payOrder = payOrderRepository.findOneByOutTradeNo(outTradeNo);
        if (null == payOrder) {
            log.info("根据商户订单号[{}]未查询到订单信息！", outTradeNo);
            return;
        }
        //切记alipaypublickey是支付宝的公钥，请去open.alipay.com对应应用下查看。
        //boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
        String alipayPublicKey = aliPayConfig.getPublicKey();
        String charset = aliPayConfig.charset;
        String signType = aliPayConfig.singType;
        //交易状态
        String tradeStatus = params.get("trade_status");
        //交易金额
        String amount = params.get("total_amount");
        //支付宝交易号
        String tradeNo = params.get("trade_no");
        //该笔交易创建的时间
        String gmtCreate = params.get("gmt_create");
        //买家付款时间
        String gmtPayment = params.get("gmt_payment");
        String gmtClose = params.get("gmt_close");
        Map<String, String> aliPayTradeStatusMap = AliPayTradeStatusEnum.aliPayTradeStatusMap();
        log.info("支付宝回调：商户订单号[{}]支付宝交易号[{}]交易金额[{}]交易状态[{}]交易创建时间[{}]买家付款时间[{}]交易结束时间[{}]", outTradeNo, tradeNo, amount, tradeStatus, gmtCreate, gmtPayment, gmtClose);
        log.info("订单交易状态[{}({})]！", aliPayTradeStatusMap.get(tradeStatus), tradeStatus);
        payOrder.setNotifyResponseInfo(JSONObject.toJSONString(requestParams));
        payOrder.setUpdateTime(new Date());
        try {
            boolean flag = AlipaySignature.rsaCheckV1(params, alipayPublicKey, charset, signType);
            if (flag) {
                log.info(">>>支付宝回调签名认证成功");
                log.info("支付宝回调：商户订单号[{}]支付宝交易号[{}]交易金额[{}]交易状态[{}]交易创建时间[{}]买家付款时间[{}]交易结束时间[{}]", outTradeNo, tradeNo, amount, tradeStatus, gmtCreate, gmtPayment, gmtClose);
                log.info("订单交易状态[{}({})]！", aliPayTradeStatusMap.get(tradeStatus), tradeStatus);
                payOrder.setTradeNo(tradeNo);
                payOrder.setTradeStatus(tradeStatus);
                payOrder.setTradeCreateTime(DateUtils.parseDate(gmtCreate));
                payOrder.setTradePaymentTime(DateUtils.parseDate(gmtPayment));
                payOrder.setTradeCloseTime(DateUtils.parseDate(gmtClose));
            } else {
                log.error(">>>支付宝回调签名认证失败");
            }
            payOrderRepository.save(payOrder);
        } catch (AlipayApiException e) {
            log.error("回调信息错误！", e);
        }
        log.info("支付宝回调结束！");
    }

    @Override
    public Result aliPayAppNotify(String outTradeNo) {
        log.info("app回调开始！");
        log.info("outTradeNo = {}", outTradeNo);
        if (StringUtils.isBlank(outTradeNo)) {
            return Result.error("商户订单号为空！");
        }
        String appPrivateKey = aliPayConfig.getPrivateKey();
        String alipayPublicKey = aliPayConfig.getPublicKey();
        String signType = aliPayConfig.singType;
        String serverUrl = aliPayConfig.url;
        String appId = aliPayConfig.getAppid();
        String charset = aliPayConfig.getCharset();
        if (StringUtils.isAnyBlank(appId, appPrivateKey, alipayPublicKey, signType, serverUrl, charset)) {
            log.error("appId = {},appPrivateKey = {},alipayPublicKey = {},signType = {},serverUrl = {},charset = {}", appId, appPrivateKey, alipayPublicKey, signType, serverUrl, charset);
            return Result.error("支付宝创建订单失败！原因：支付宝配置信息错误,请检查！");
        }
        PayOrder payOrder = payOrderRepository.findOneByOutTradeNo(outTradeNo);
        if (null == payOrder) {
            return Result.error(String.format("根据商户订单号[%s]未查询到订单信息！", outTradeNo));
        }
        if (StringUtils.isBlank(payOrder.getTradeStatus())) {
//            支付宝没有回调延迟 主动查询
            try {
                /**
                 * https://opendocs.alipay.com/apis/028xq9
                 * 参考
                 */
                AlipayClient alipayClient = new DefaultAlipayClient(serverUrl, appId, appPrivateKey, "json", charset, alipayPublicKey, signType);
                AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
                JSONObject bizContent = new JSONObject();
                bizContent.put("out_trade_no", outTradeNo);
                request.setBizContent(bizContent.toString());
                AlipayTradeQueryResponse response = alipayClient.execute(request);
                log.info("查询订单响应：{}", JSONObject.toJSONString(response));
                payOrder.setQueryResponseInfo(JSONObject.toJSONString(response));
                payOrder.setTradeNo(response.getTradeNo());
                payOrder.setTradeStatus(response.getTradeStatus());
                payOrder.setUpdateTime(new Date());
                payOrderRepository.save(payOrder);
                if (response.isSuccess()) {
                    log.info("订单查询成功！");
                    return Result.success("回调成功！", payOrder.getTradeStatus());
                } else {
                    log.error("订单查询失败！错误码[{}]原因[{}]明细错误码[{}]明细返回码描述[{}]", response.getCode(), response.getMsg(), response.getSubCode(), response.getSubMsg());
                    return Result.error(response.getSubMsg());
                }
            } catch (AlipayApiException e) {
                log.error("根据订单号[{}]查询支付订单信息失败！", outTradeNo, e);
                return Result.error("查询订单出现异常，请稍后重试！");
            }
        }
        log.info("app回调结束！");
        return Result.success("回调成功！", payOrder.getTradeStatus());
    }
}
